package com.griddroid.computations;

import com.griddroid.exceptions.NonCoherentComputationException;
import com.griddroid.exceptions.NotCalculatedComputationException;

/**
 * Another implementation of a computation for GridDroid. This one calculates a
 * product on a mathematical formula with one variable
 * 
 * @author Charalampos Mavroforakis
 * @author Thomas Ailianos
 * @author Michail Michailidis
 * 
 */
public class Product extends Computation
{

	private static final long serialVersionUID = -7411721493598910627L;
	private int base;
	private int end;
	private String function;

	/**
	 * 
	 * @param base
	 *            the lower bound of the product
	 * @param end
	 *            the upper bound of the product
	 * @param function
	 *            the mathematical formula on which to apply the product
	 */
	public Product(final int base, final int end, final String function)
	{
		this.base = base;
		this.end = end;
		this.function = function;
	}

	/**
	 * Just a default constructor with no typical use. It sets the following
	 * values: <code>
	 * base = 0;
	 * end = 0;
	 * function = "";
     * </code>
	 */
	public Product()
	{
		this.base = 0;
		this.end = 0;
		this.function = "";
	}

	/**
	 * To split a Product, we just split the range on which it should be
	 * evaluated. So, e.g. if the original product should be calculated from 0
	 * to 10 if we split it in 10 parts, each small Product will be calculated
	 * on the range <i>i</i> to <i>i</i>+1 (with <i>i</i> ranging from 0 to 9)
	 * 
	 * @param parts
	 *            the number of parts to split the Product into
	 * @return an array of smaller Products, that result from the original one
	 */
	@Override
	public Product[] split(int parts)
	{
		int temp_begin = this.base;
		int step = (this.end - this.base + 1) / parts;
		int temp_end;

		if (step == 0)
		{
			// if parts are too many, then split into fewer parts
			parts = this.end - this.base + 1;
			step = 1;
		}

		final Product[] small_products = new Product[parts];
		for (int i = 0; i < parts; i++)
		{
			temp_end = (i != parts - 1) ? temp_begin + step - 1 : this.end;
			small_products[i] = new Product(temp_begin, temp_end, this.function);
			temp_begin = temp_end + 1;
		}
		return small_products;
	}

	/**
	 * Merges an array of Products into a new one. The final result is the
	 * product of all the calculated sub-products
	 * 
	 * @param parts
	 *            array with calculated parts from the same computation
	 * @throws NotCalculatedComputationException
	 *             one or more parts of the array has not been calculated
	 * @throws NonCoherentComputationException
	 *             one or more parts of the array is not a Product
	 */
	@Override
	public void merge(final Computation[] parts) throws NotCalculatedComputationException, NonCoherentComputationException
	{
		double product = 1D;

		for (final Computation c : parts)
		{
			if (!(c instanceof Product))
			{
				throw new NonCoherentComputationException();
			}
			product *= c.getResult();
		}

		super.calculated = true;
		super.result = product;
	}

	/**
	 * Executes the calculation of the product. It is applied on the given
	 * mathematical formula with the (single) variable ranging between
	 * (included) the lower and upper bounds
	 * <p>
	 * Does not return anything, just sets the {@link Computation#result result}
	 * value. To get the actual result, make a call on
	 * {@link Computation#getResult() getResult()}
	 */
	@Override
	public void calculate()
	{
		super.calculate();
		char var = '\0';
		for (final char c : function.toCharArray())
		{
			if (Character.isLetter(c))
			{
				var = c;
			}
		}
		float res = 1;

		for (int i = base; i <= end; i++)
		{
			res *= evaluateExpression(new String[] { "" + var }, new double[] { new Double(i) }, function);
		}
		super.result = res;
	}

	/**
	 * 
	 * @return a String (latex like) representation of the product
	 */
	@Override
	public String toString()
	{
		return "\\product_{i=" + this.base + "}^{" + this.end + "}{" + this.function + "}";
	}

	/**
	 * 
	 * @return the lower bound of the product
	 */
	public int getBase()
	{
		return base;
	}

	/**
	 * 
	 * @return the upper bound of the product
	 */
	public int getEnd()
	{
		return end;
	}

	/**
	 * 
	 * @return the mathematical formula on which the product should be evaluated
	 */
	public String getFunction()
	{
		return function;
	}

	/**
	 * 
	 * @param base
	 *            the lower bound of the product
	 */
	public void setBase(final int base)
	{
		this.base = base;
	}

	/**
	 * 
	 * @param end
	 *            the upper bound of the product
	 */
	public void setEnd(final int end)
	{
		this.end = end;
	}

	/**
	 * @param function
	 *            string with a mathematical formula. It should have only one
	 *            variable over which to calculate the product (multiple
	 *            products not yet supported)
	 */
	public void setFunction(final String function)
	{
		this.function = function;
	}

	/**
	 * Just a testing function for the Product alone, to check the functionality
	 * of the class
	 * 
	 * @param args
	 */
	public static void main(final String args[])
	{
		final Product p = new Product(-12, 100, "cos(i^2)");
		final Product[] products = p.split(11);
		for (final Product prod : products)
		{
			prod.calculate();
			System.out.println(prod.toString());
		}
		final Computation c = new Product();

		try
		{
			c.merge(products);
			System.out.println("_____RES: " + c.getResult());
		}
		catch (final NotCalculatedComputationException e)
		{
			e.printStackTrace();
		}
		catch (final NonCoherentComputationException e)
		{
			e.printStackTrace();
		}
	}

}
